#pragma once

#include <list>
#include <memory>
#include <string>
#include <windows.h>

#include "NonCopyable.h"

namespace TestFramework
{
    void TraceError(const std::string& description, const std::string& fileName,
                    int line, const std::string& signature);

    class TestCase : NonCopyable
    {
    public:
        virtual void TestMethod() = 0;

    public:
        bool IsFailed;
        bool ExceptionRaised;
    };

    typedef std::shared_ptr<TestCase> TestCasePtr;
    typedef std::list<TestCasePtr> TestCasesList;

    class CaseProcessor : NonCopyable
    {
    public:
        virtual void Process(const TestCasePtr& testCase) = 0;
    };

    class CasesAccumulator : NonCopyable
    {
    public:
        virtual void AddCase(const TestCasePtr& testCase) = 0;
        virtual void Clear() = 0;
        virtual void Enumerate(CaseProcessor& processor) = 0;
        virtual int GetCasesCount() const = 0;
    };
    
    class UnitTest : NonCopyable
    {
    public:
        virtual void RegisterTests(CasesAccumulator& accumulator) = 0;
        virtual std::string GetName() const = 0;
    };

    typedef std::shared_ptr<UnitTest> UnitTestPtr;
    typedef std::list<UnitTestPtr> UnitTestList;

    class TestManager
    {
    public:
        TestManager();
        void AddTest(const UnitTestPtr& test);
        void RunTests();
        void ShowResultSummary() const;

    private:
        UnitTestList Tests;
        int Failes;
        int Success;
    };
}

#define CHECK(expression) \
if (!(expression)) \
{ \
    TestFramework::TraceError(std::string("Expression result is FALSE"), __FILE__, __LINE__, __FUNCSIG__); \
    IsFailed = true; \
}

#define CHECK_EXCEPTION_OCCURRENCE(expression) \
ExceptionRaised = false; \
try \
{ \
    expression; \
} \
catch (...) \
{ \
    ExceptionRaised = true; \
} \
if (!ExceptionRaised) \
{ \
    TestFramework::TraceError(std::string("Exception is absent!"), __FILE__, __LINE__, __FUNCSIG__); \
    IsFailed = true; \
}

#define CHECK_EXCEPTION_ABSENT(expression) \
try \
{ \
    expression; \
} \
catch (const std::exception& error) \
{ \
    TestFramework::TraceError(error.what(), __FILE__, __LINE__, __FUNCSIG__); \
    IsFailed = true; \
} \
catch (...) \
{ \
    TestFramework::TraceError(std::string("An unknown error occured!"), __FILE__, __LINE__, __FUNCSIG__); \
    IsFailed = true; \
}

#define CHECK_GREATER_PERFORMANCE \
if ((EtalonFinish - EtalonStart) < (TestFinish - TestStart)) \
{ \
    TestFramework::TraceError(std::string("Performance of etalon is higher"), __FILE__, __LINE__, __FUNCSIG__); \
    IsFailed = true; \
}

#define TEST_CASE( test_name ) \
class test_name : public TestFramework::TestCase \
{ \
public: \
    test_name() {IsFailed = false; ExceptionRaised = false;} \
    virtual void TestMethod(); \
}; \
void test_name::TestMethod()

#define PERFORMANCE_TEST_CASE( test_name ) \
class test_name : public TestFramework::TestCase \
{ \
private: \
    unsigned long long EtalonStart, EtalonFinish; \
    unsigned long long TestStart, TestFinish; \
public: \
    test_name() {IsFailed = false; ExceptionRaised = false; EtalonStart = EtalonFinish = TestStart = TestFinish = 0;} \
    virtual void TestMethod(); \
}; \
void test_name::TestMethod()

#define START_ETALON EtalonStart = GetTickCount();
#define FINISH_ETALON EtalonFinish = GetTickCount();
#define START_TEST TestStart = GetTickCount();
#define FINISH_TEST TestFinish = GetTickCount();

#define REGISTER_TEST( tes_name ) accumulator.AddCase(TestFramework::TestCasePtr(new tes_name()));

#define START_TEST_SUITE( suite_name, suite_description ) \
class suite_name : public TestFramework::UnitTest \
{ \
public: \
    virtual std::string GetName() const\
    { \
        return suite_description; \
    } \
    virtual void RegisterTests(TestFramework::CasesAccumulator& accumulator)
    
#define FINISH_TEST_SUITE };

#define CREATE_SUITE( suite_name ) return TestFramework::UnitTestPtr(new suite_name());
